{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# DPA攻击AES\n",
    "\n",
    "支持的设备:\n",
    "\n",
    "SCOPES:\n",
    "\n",
    "* OPENADC\n",
    "* CWNANO\n",
    "\n",
    "PLATFORMS:\n",
    "\n",
    "* CWLITEARM\n",
    "* CWLITEXMEGA\n",
    "* CWNANO"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "开始此教程之前，请确保你能够明白差分功耗分析中的一些概念，我们建议你先完成DPA的前置教程学习。如果你已经完成，那让我们来看看DPA是如何运作的吧。"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DPA攻击理论\n",
    "\n",
    "在前面的DPA教程中，AES中S盒操作输出的汉明重量和芯片消耗的能量有关，事实证明，仅仅是这种关系（而不是任何更强的东西，如线性程度）就足以破解AES密钥的信息。我们有不同的方法，单在此教程中，我们将寻找平均值之间的差异。使用这种方式，我们的目标是在SBox输出的结果中用一个比特来进行分组（哪个比特并不重要）：如果比特是1，平均而言，它的一组痕迹在S盒运行期间的能耗应该比另一组高。\n",
    "\n",
    "两组之间的平均值是否有很大的差异？这取决于我们分组是否合理，如果没有的话，那么两组的平均差异不会太大。我们回顾一下S盒的操作。\n",
    "\n",
    "![](img/Sbox_cpa_detail.png)\n",
    "\n",
    "S盒的输出依赖于未知的子密钥，然而，由于正确的密钥将会拥有较大的平均差异，而可能性较小的字密钥则拥有较小的平均差异，所以我们有一种方法来检查某个字密钥是否正确。如果我们计算每个子密钥的平均值之差，正确的子密钥将有最大的平均值之差。\n",
    "\n",
    "* 步骤如下\n",
    "\n",
    "    1. 捕获多组能量轨迹。\n",
    "    2. 对S盒输出的最低位能轨进行统计分组。\n",
    "    3. 计算均值差。\n",
    "    4. 重复猜测子密钥。\n",
    "    5. 最大的差值则为正确的子密钥。\n",
    "    6. 对每个子密钥重复上述步骤。\n",
    "\n",
    "最后，我们应该就可以得到正确的AES密钥。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPETYPE = 'OPENADC'\n",
    "PLATFORM = 'CWLITEARM'\n",
    "CRYPTO_TARGET = 'TINYAES128C'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash -s \"$PLATFORM\" \"$CRYPTO_TARGET\"\n",
    "cd ../../hardware/victims/firmware/simpleserial-aes\n",
    "make PLATFORM=$1 CRYPTO_TARGET=$2"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 捕获能轨"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "捕获和设置与先前的教程相似。我们必须捕获相当数量的轨迹（这里通常是几千条），因为 \"均值差 \"并不是一个超级有效的轨迹方法。正如你在CPA教程中发现的那样，CPA在这方面要好得多——它通常可以在50个轨迹以下就能破解像这样的AES实现。\n",
    "\n",
    "你可能还会发现，你需要修改增益设置和捕获的轨迹数量——这种攻击对增益设置和噪音比CPA攻击要敏感得多。"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"Helper_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fw_path = \"../../hardware/victims/firmware/simpleserial-aes/simpleserial-aes-{}.hex\".format(PLATFORM)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cw.program_target(scope, prog, fw_path)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 捕获"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"Helper_Scripts/plot.ipynb\"\n",
    "plot = real_time_plot(plot_len=3000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Capture Traces\n",
    "from tqdm import tnrange, trange\n",
    "import numpy as np\n",
    "import time\n",
    "\n",
    "ktp = cw.ktp.Basic()\n",
    "\n",
    "traces = []\n",
    "N = 2500  # Number of traces\n",
    "\n",
    "if PLATFORM == \"CWLITEARM\" or PLATFORM == \"CW308_STM32F3\":\n",
    "    scope.adc.samples = 4000\n",
    "elif PLATFORM == \"CWLITEXMEGA\" or PLATFORM == \"CW303\":\n",
    "    scope.gain.db = 20\n",
    "    scope.adc.samples = 1700 - 170\n",
    "    scope.adc.offset = 500 + 700 + 170\n",
    "    N = 5000\n",
    "    \n",
    "print(scope)\n",
    "for i in trange(N, desc='Capturing traces'):\n",
    "    key, text = ktp.next()  # manual creation of a key, text pair can be substituted here\n",
    "\n",
    "    trace = cw.capture_trace(scope, target, text, key)\n",
    "    if trace is None:\n",
    "        continue\n",
    "    traces.append(trace)\n",
    "    plot.send(trace)\n",
    "\n",
    "#Convert traces to numpy arrays\n",
    "trace_array = np.asarray([trace.wave for trace in traces])\n",
    "textin_array = np.asarray([trace.textin for trace in traces])\n",
    "known_keys = np.asarray([trace.key for trace in traces])  # for fixed key, these keys are all the same"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 分析"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "正如我们上面所讨论的，我们在这里的目标是在可能的子密钥值中找到最大的均值差。首先，我们将得到一些对我们的计算有用的值和函数。我们将在后面使用`intermediate()`来从明文和密钥输入中获得S盒的输出。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numtraces = np.shape(trace_array)[0] #total number of traces\n",
    "numpoints = np.shape(trace_array)[1] #samples per trace\n",
    "\n",
    "sbox = (\n",
    "    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,\n",
    "    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,\n",
    "    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,\n",
    "    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,\n",
    "    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,\n",
    "    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,\n",
    "    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,\n",
    "    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,\n",
    "    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,\n",
    "    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,\n",
    "    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,\n",
    "    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,\n",
    "    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,\n",
    "    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,\n",
    "    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,\n",
    "    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)\n",
    "\n",
    "def intermediate(pt, keyguess):\n",
    "    return sbox[pt ^ keyguess]"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们的第一步是根据S盒的输出，将我们的轨迹分成不同的组。如前所述，我们根据最低有效位进行分离，但实际上任何位都可以（作为测试，你可以改变这一点，看看攻击是否仍然有效）：\n",
    "\n",
    "```Python\n",
    "one_list = []\n",
    "zero_list = []\n",
    "for tnum in range(numtraces):\n",
    "    if (intermediate(textin_array[tnum][subkey], kguess) & 1):\n",
    "        one_list.append(trace_array[tnum])\n",
    "    else:\n",
    "        zero_list.append(trace_array[tnum])\n",
    "```\n",
    "\n",
    "计算均值差：\n",
    "\n",
    "```Python\n",
    "one_avg = np.asarray(one_list).mean(axis=0)\n",
    "zero_avg = np.asarray(zero_list).mean(axis=0)\n",
    "mean_diffs[kguess] = np.max(abs(one_avg - zero_avg))\n",
    "```\n",
    "\n",
    "我们需要对每个可能的密钥猜测进行操作，然后挑选出均值差最大的一个：\n",
    "```Python\n",
    "guess = np.argsort(mean_diffs)[-1]\n",
    "key_guess.append(guess)\n",
    "print(hex(guess))\n",
    "print(mean_diffs[guess])\n",
    "```\n",
    "\n",
    "最后，攻击所有的子密钥："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tqdm import tnrange\n",
    "import numpy as np\n",
    "mean_diffs = np.zeros(255)\n",
    "key_guess = []\n",
    "known_key = known_keys[0]\n",
    "plots = []\n",
    "for subkey in tnrange(0, 16, desc=\"Attacking Subkey\"):\n",
    "    for kguess in tnrange(255, desc=\"Keyguess\", leave=False):\n",
    "        one_list = []\n",
    "        zero_list = []\n",
    "        \n",
    "        for tnum in range(numtraces):\n",
    "            if (intermediate(textin_array[tnum][subkey], kguess) & 1): #LSB is 1\n",
    "                one_list.append(trace_array[tnum])\n",
    "            else:\n",
    "                zero_list.append(trace_array[tnum])\n",
    "        one_avg = np.asarray(one_list).mean(axis=0)\n",
    "        zero_avg = np.asarray(zero_list).mean(axis=0)\n",
    "        mean_diffs[kguess] = np.max(abs(one_avg - zero_avg))\n",
    "        if kguess == known_key[subkey]:\n",
    "            plots.append(abs(one_avg - zero_avg))\n",
    "    guess = np.argsort(mean_diffs)[-1]\n",
    "    key_guess.append(guess)\n",
    "    print(hex(guess) + \"(real = 0x{:02X})\".format(known_key[subkey]))\n",
    "    #mean_diffs.sort()\n",
    "    print(mean_diffs[guess])\n",
    "    print(mean_diffs[known_key[subkey]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(key_guess)\n",
    "print(known_key)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们还可以绘制一些正确的子密钥字节的均值差："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib notebook\n",
    "import matplotlib.pylab as plt\n",
    "\n",
    "for i in range(16):\n",
    "    plt.plot(plots[i])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 幽灵峰问题\n",
    "\n",
    "* 这是由于硬件设置导致的，我们不用对此过多考虑，详情见[论文](https://eprint.iacr.org/2005/311.pdf)。\n",
    "* （Xenny）不过我在捕获的时候没遇到这个问题。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.9.12 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.9.12 (main, Mar 26 2022, 15:51:13) \n[Clang 12.0.0 (clang-1200.0.32.29)]"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
